Skip to content

Serialize per-target compiles (CompileScheduler + zinc per-scope lock)#92

Merged
rochala merged 1 commit into
mainfrom
feat/compile-concurrency
Jun 15, 2026
Merged

Serialize per-target compiles (CompileScheduler + zinc per-scope lock)#92
rochala merged 1 commit into
mainfrom
feat/compile-concurrency

Conversation

@rochala

@rochala rochala commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

What

Concurrent didSave compiles of the same build target used to race into the same per-scope zinc analysis store and output jar (${scopeId}-analysis / ${scopeId}-classes), corrupting each other. This serializes them at two layers:

  • CompileScheduler (new) — a per-key conflating scheduler. At most one compile per target runs at a time; while one is in flight, further saves coalesce into a single pending re-run that keeps only the newest (intermediate saves dropped). ServerImpl.handleDidSave now schedules through it instead of supervising a fresh fiber per save.
  • ZincCliServer per-scope Mutex — serializes same-target compiles as a safety net (distinct targets stay parallel). ZincCliServer.create wires the lock map.

Test

  • DidSaveCompileConcurrencySpec (new) — regression guard: a CSP probe records peak concurrency; rapid successive saves of one target must keep it at 1. TestServer.resource gains an optional cspServer injection so the spec can supply the probe (callers in TestClientSpec updated for the new signature).

Notes

  • Split out of a larger working-tree batch; observability/tracing and TASTy-diagnostics changes will follow in separate PRs.
  • Verified: ./mill __.compile clean and ./mill sls.test green (598/598).

🤖 Generated with Claude Code

Rapid edit-save-edit-save used to fork one compile fiber per didSave, all
racing into the same per-scope zinc analysis store and output jar
(`${scopeId}-analysis` / `${scopeId}-classes`), corrupting each other.

- CompileScheduler: a per-key conflating scheduler. Each target has a
  circularBuffer(1) mailbox drained by one worker fiber, so a burst of saves
  collapses to "the in-flight compile + at most one newest pending"; the
  buffer's evict-oldest policy is what makes the latest save win (dropping(1)
  would keep the oldest and discard newer saves). handleDidSave schedules
  through it instead of supervising a fresh fiber per save.
- ZincCliServer: a per-scopeId Mutex serializes same-target compiles as a
  safety net (distinct targets stay parallel). ZincCliServer.create wires the
  lock map.
- DidSaveCompileConcurrencySpec: regression guard asserting peak compile
  concurrency stays 1 for the same target. TestServer gains an optional
  cspServer injection so the spec can supply a concurrency probe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rochala rochala force-pushed the feat/compile-concurrency branch from f57f97c to 12fe9e8 Compare June 15, 2026 21:06
@rochala rochala marked this pull request as ready for review June 15, 2026 21:08
@rochala rochala merged commit 37630f0 into main Jun 15, 2026
1 check passed
@rochala rochala deleted the feat/compile-concurrency branch June 15, 2026 21:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant